package com.android.lovely.api.ms

import android.util.Base64
import com.android.lovely.api.ApiResp
import com.android.lovely.api.LovelyException
import com.android.lovely.api.encrypt.decryptAES
import com.android.lovely.api.manager.contactUrl
import com.android.lovely.api.manager.netWorkChangeState
import com.android.lovely.framework.UIController.showToast
import com.android.lovely.user.UserManager
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import okhttp3.ResponseBody
import okio.BufferedSource
import okio.ByteString.Companion.decodeHex
import org.json.JSONObject
import retrofit2.Converter
import java.io.IOException
import java.lang.reflect.Type
import java.util.*

@Suppress("SpellCheckingInspection")
class MoshiResponseBodyConverter<T>(
    private val adapter: JsonAdapter<T>, private val moshi: Moshi, private val type: Type
) : Converter<ResponseBody, T> {

    private val rawType: Type = Types.getRawType(type)

    @Throws(IOException::class)
    override fun convert(value: ResponseBody): T? {
        return parseResponse(value)
    }

    private fun parseResponse(value: ResponseBody): T? {
        value.use {
            val source = it.source()
            if (source.rangeEquals(0, UTF8_BOM)) {
                source.skip(UTF8_BOM.size.toLong())
            }
            val sourceText = decryptSource(source)
            val json = JSONObject(sourceText)
            handleResponse(json.optInt("code", 1), json.optString("msg"), json.optString("data"))
            if (rawType == ApiResp::class.java) {
                return adapter.fromJson(sourceText)
            }
            val type = Types.newParameterizedType(ApiResp::class.java, type)
            val apiResponse = moshi.adapter<ApiResp<T>>(type).fromJson(sourceText)
            if (apiResponse != null) {
                fitNull(apiResponse)
                return apiResponse.data
            }
            return apiResponse
        }
    }

    private fun fitNull(apiResp: ApiResp<T>) {
        if (apiResp.data == null) {
            if (rawType === Any::class.java) {
                apiResp.data = JSONObject.NULL as T
            } else if (rawType === String::class.java) {
                apiResp.data = "" as T
            } else if (rawType === Int::class.javaPrimitiveType || type === Int::class.java) {
                apiResp.data = 0 as T
            } else if (rawType === Boolean::class.javaPrimitiveType || type === Boolean::class.java) {
                apiResp.data = false as T
            } else if (rawType === Long::class.javaPrimitiveType || type === Long::class.java) {
                apiResp.data = 0 as T
            } else if (rawType === MutableList::class.java || rawType === ArrayList::class.java) {
                apiResp.data = adapter.fromJson("[]")
            } else if (rawType === LinkedList::class.java) {
                apiResp.data = adapter.fromJson("[]")
            } else {
                apiResp.data = adapter.fromJson("{}")
            }
        }
    }

    private fun decryptSource(source: BufferedSource): String =
        Base64.decode(source.readString(Charsets.UTF_8), Base64.NO_WRAP).decodeToString()
            .decryptAES()

    private fun handleResponse(code: Int, message: String?, data: String?) {
        when (code) {
            -1 -> {
                netWorkChangeState.tryEmit(-1)
                throw LovelyException(message)
            }

            -2 -> {
                UserManager.logout()
                netWorkChangeState.tryEmit(-2)
                throw LovelyException(message)
            }

            -3 -> {
                UserManager.logout()
                if (!data.isNullOrEmpty()) {
                    runCatching {
                        contactUrl = JSONObject(data).optString("url").orEmpty()
                    }
                }
                netWorkChangeState.tryEmit(-3)
                throw LovelyException(message)
            }

            0 -> {
                showToast(message.orEmpty())
                throw LovelyException(message)
            }
        }
    }

    companion object {
        private val UTF8_BOM = "EFBBBF".decodeHex()
    }
}